package org.meb.conquest.db;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import lombok.Setter;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.JsonProcessingException;
import org.meb.conquest.db.dao.CardBaseDao;
import org.meb.conquest.db.dao.CardSetBaseDao;
import org.meb.conquest.db.dao.CycleBaseDao;
import org.meb.conquest.db.dao.DomainBaseDao;
import org.meb.conquest.db.model.CardBase;
import org.meb.conquest.db.model.CardSetBase;
import org.meb.conquest.db.model.CycleBase;
import org.meb.conquest.db.model.DomainBase;
import org.meb.conquest.db.model.DomainLang;
import org.meb.conquest.db.model.IBase;
import org.meb.conquest.db.model.ILang;
import org.meb.conquest.db.util.JpaUtils;
import org.meb.conquest.db.util.Utils;
import org.meb.conquest.json.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DataLoader {

	private static final String DATA_BASE;
	private static final String JSON_BASE;
	private static final String RAW_BASE;

	static {
		String dropboxHome = System.getProperty("dropbox.home");
		if (StringUtils.isBlank(dropboxHome)) {
			throw new IllegalStateException("Dropbox home not set");
		}
		String place = System.getProperty("place");
		if (StringUtils.isBlank(place)) {
			throw new IllegalStateException("Place not set");
		}
		String dateString = new SimpleDateFormat("yyyyMMdd").format(new Date());
		DATA_BASE = dropboxHome + "/workspace/cqstlcg/data/";
		RAW_BASE = DATA_BASE + "_raw/";
		JSON_BASE = DATA_BASE + "json" + dateString + "-" + place + "/";
		if (!new File(JSON_BASE).exists()) {
			new File(JSON_BASE).mkdir();
		}
	}
	private static final String DOMAIN_FILE_NAME = JSON_BASE + "domain.json";
	private static final String CYCLE_FILE_NAME = JSON_BASE + "cycle.json";
	private static final String CARD_SET_FILE_NAME = JSON_BASE + "card_set.json";
	private static final String CARD_FILE_NAME = JSON_BASE + "card.json";

	private static final Logger log = LoggerFactory.getLogger(DataLoader.class);

	private EntityManager em;
	private DomainBaseDao dbDao;
	private CycleBaseDao ccbDao;
	private CardSetBaseDao csbDao;
	private CardBaseDao cbDao;

	@Setter
	private boolean langItemsOnly = true;
	private boolean rollback = false;
	private boolean globalTransaction = true;

	public static void main(String[] args) throws IOException {
		DataLoader loader = new DataLoader();
		if (args[0].equals("--import")) {
			loader.importDatabaseFromJson();
		} else if (args[0].equals("--export")) {
			loader.exportDatabaseToJson();
		} else if (args[0].equals("--import-flat")) {
			loader.importDomainsFromFlatFile();
		} else {
			throw new IllegalArgumentException("Invalid argument");
		}
	}

	public DataLoader() {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("cqstlcgUnit");
		em = emf.createEntityManager();
		dbDao = new DomainBaseDao(em);
		ccbDao = new CycleBaseDao(em);
		csbDao = new CardSetBaseDao(em);
		cbDao = new CardBaseDao(em);
	}

	public void importDatabaseFromJson() throws IOException {
		langItemsOnly = false;
		globalTransaction = true;

		if (globalTransaction) {
			em.getTransaction().begin();
		}

		// writeDomainsToDatabase();
		writeCyclesToDatabase();
		writeCardSetsToDatabase();
		writeCardsToDatabase();

		if (globalTransaction) {
			em.getTransaction().commit();
		}
	}

	public void exportDatabaseToJson() throws IOException {
		List<DomainBase> dbList = readDomainsFromDatabase();
		writeDomainsToJsonFile(dbList);
		List<CycleBase> ccbList = readCyclesFromDatabase();
		writeCyclesToJsonFile(ccbList);
		List<CardSetBase> csbList = readCardSetsFromDatabase();
		writeCardSetsToJsonFile(csbList);
		List<CardBase> cbList = readCardsFromDatabase();
		writeCardsToJsonFile(cbList);
	}

	public void importDomainsFromFlatFile() throws FileNotFoundException, IOException {

		List<String> lines = IOUtils.readLines(new FileInputStream(
				new File(RAW_BASE + "domain.txt")));

		// if (!globalTransaction) {
		em.getTransaction().begin();
		// }

		String domain = null;
		String[] langs = { "en", "pl" };

		Iterator<String> iter = lines.iterator();
		while (iter.hasNext()) {
			String line = iter.next();
			if (StringUtils.isBlank(line)) {
				continue;
			}

			if (line.trim().matches(":[A-Z ]+:")) {
				domain = Utils.toTechName(line.trim());
			} else {
				if (StringUtils.isBlank(domain)) {
					throw new IllegalStateException("Domain is blank");
				}

				String[] tokens = line.replaceAll("\t+", "\t").split("\\t");
				DomainBase db = new DomainBase();
				db.setDomain(domain);
				db.setRecordState("A");
				db.setValue(Utils.toTechName(tokens[0]));

				for (int i = 0; i < tokens.length; i++) {
					DomainLang dl = new DomainLang();
					dl.setBase(db);
					dl.setRecordState("A");
					dl.setLangCode(langs[i]);
					dl.setDescription(tokens[i]);
					db.getLangItems().put(langs[i], dl);
				}
				em.persist(db);
				em.flush();
			}
		}

		// if (!globalTransaction) {
		em.getTransaction().commit();
		// }
	}

	public void writeDomainsToDatabase() throws IOException {
		DomainBase[] baseNewArray = JsonUtils.read(DOMAIN_FILE_NAME, DomainBase[].class);

		if (!globalTransaction) {
			em.getTransaction().begin();
		}
		for (DomainBase baseNew : baseNewArray) {
			DomainBase base = dbDao.findUnique(baseNew);
			if (base == null) {
				persist(baseNew);
			} else {
				merge(base, baseNew);
			}
			em.flush();
		}
		if (!globalTransaction) {
			em.getTransaction().commit();
		}
	}

	public void writeCardSetsToDatabase() throws IOException {
		CardSetBase[] baseNewArray = JsonUtils.read(CARD_SET_FILE_NAME, CardSetBase[].class);

		if (!globalTransaction) {
			em.getTransaction().begin();
		}
		for (CardSetBase baseNew : baseNewArray) {
			CardSetBase base = csbDao.findUnique(baseNew);
			if (base == null) {
				if (baseNew.getCycleBase() != null) {
					// is part of a cycle
					CycleBase ccb = ccbDao.findUnique(baseNew.getCycleBase());
					if (ccb == null) {
						throw new RuntimeException("Unable to find cycle for card set: "
								+ baseNew.getTechName());
					}
					baseNew.setCycleBase(ccb);
				}
				persist(baseNew);
			} else {
				merge(base, baseNew);
			}
			em.flush();
		}
		if (!globalTransaction) {
			em.getTransaction().commit();
		}
	}

	public void writeCardsToDatabase() throws IOException {
		CardBase[] baseNewArray = JsonUtils.read(CARD_FILE_NAME, CardBase[].class);

		if (!globalTransaction) {
			em.getTransaction().begin();
		}
		for (CardBase baseNew : baseNewArray) {
			// Map<String, CardLang> langItems = baseNew.getLangItems();
			// if (langItems == null || langItems.get("pl") == null
			// || langItems.get("pl").getFaqText() == null) {
			// continue;
			// }

			CardBase base = cbDao.findUnique(baseNew);
			if (base == null) {
				CardSetBase csb = csbDao.findUnique(baseNew.getCardSetBase());
				if (csb == null) {
					throw new RuntimeException("Unable to find card set for card: "
							+ baseNew.getTechName());
				}
				baseNew.setCardSetBase(csb);

				persist(baseNew);
			} else {
				merge(base, baseNew);
			}
			em.flush();
		}
		if (!globalTransaction) {
			if (rollback) {
				em.getTransaction().rollback();
			} else {
				em.getTransaction().commit();
			}
		}
	}

	public void writeCyclesToDatabase() throws IOException {
		CycleBase[] baseNewArray = JsonUtils.read(CYCLE_FILE_NAME, CycleBase[].class);

		if (!globalTransaction) {
			em.getTransaction().begin();
		}
		for (CycleBase baseNew : baseNewArray) {
			CycleBase base = ccbDao.findUnique(baseNew);
			if (base == null) {
				JpaUtils.smartPersist(em, baseNew);
			} else {
				JpaUtils.smartMerge(em, base, baseNew);
			}
			em.flush();
		}
		if (!globalTransaction) {
			em.getTransaction().commit();
		}
	}

	public List<DomainBase> readDomainsFromDatabase() {
		return dbDao.find(new DomainBase());
	}

	public List<CycleBase> readCyclesFromDatabase() {
		return ccbDao.find(new CycleBase());
	}

	public List<CardSetBase> readCardSetsFromDatabase() {
		return csbDao.find(new CardSetBase());
	}

	public List<CardBase> readCardsFromDatabase() {
		CardBase example = new CardBase();
		example.setCardSetBase(new CardSetBase("on-the-doorstep"));
		return cbDao.find(example);
	}

	public void writeDomainsToJsonFile(List<DomainBase> dbList) throws JsonProcessingException,
			IOException {
		JsonUtils.write(dbList, DOMAIN_FILE_NAME);
	}

	public void writeCyclesToJsonFile(List<CycleBase> ccbList) throws JsonProcessingException,
			IOException {
		JsonUtils.write(ccbList, CYCLE_FILE_NAME);
	}

	public void writeCardSetsToJsonFile(List<CardSetBase> csbList) throws JsonProcessingException,
			IOException {
		for (CardSetBase csb : csbList) {
			CycleBase ccb = csb.getCycleBase();
			if (ccb != null) {
				ccb = new CycleBase();
				ccb.setTechName(csb.getCycleBase().getTechName());
				csb.setCycleBase(ccb);
			}
		}
		JsonUtils.write(csbList, CARD_SET_FILE_NAME);
	}

	public void writeCardsToJsonFile(List<CardBase> cbList) throws JsonProcessingException,
			IOException {

		// int c = 0;
		// Iterator<CardBase> iter = cbList.iterator();
		// while (iter.hasNext()) {
		// CardBase cb = iter.next();
		// if (cb.getTypeCode() != CardType.QUEST) {
		// iter.remove();
		// continue;
		// }
		// // if (cb.getEncounterSetBase() == null) {
		// // iter.remove();
		// // continue;
		// // }
		//
		// // if (cb.getLangItems().get("pl") == null) {
		// // CardLang lang = new CardLang("pl");
		// // lang.setText(" ");
		// // lang.setName(" ");
		// // cb.getLangItems().put("pl", lang);
		// // }
		// c++;
		// }
		// System.out.println(c);

		for (CardBase cb : cbList) {
			CardSetBase csb = cb.getCardSetBase();
			if (csb != null) {
				csb = new CardSetBase();
				csb.setTechName(cb.getCardSetBase().getTechName());
				cb.setCardSetBase(csb);
			}
		}
		JsonUtils.write(cbList, CARD_FILE_NAME);
	}

	private <B extends IBase<L>, L extends ILang<B>> void persist(B baseNew) {
		JpaUtils.smartPersist(em, baseNew);
	}

	private <B extends IBase<L>, L extends ILang<B>> void merge(B base, B baseNew) {
		JpaUtils.smartMerge(em, base, baseNew, langItemsOnly);
	}
}
